package jd

import (
	"bytes"
	"context"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/nilorg/sdk/convert"
	"github.com/tidwall/gjson"
	"io"
	"log"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"
)

type Parameters map[string]any

type Client struct {
	opts *Options
}

func NewClient(opts ...Option) *Client {
	client := new(Client)
	client.opts = NewOptions(opts...)
	return client
}

func (c *Client) do(req *http.Request) (*http.Response, error) {
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
	return c.opts.httpClient.Do(req)
}

func (c *Client) Execute(ctx context.Context, method string, params Parameters) (result gjson.Result, err error) {
	err = c.checkConfig()
	if err != nil {
		return
	}
	if c.opts.JsonSimplify {
		params["simplify"] = "true"
	}
	params["method"] = method
	params.setRequestData(ctx, c.opts.AppKey, c.opts.AppSecret)

	return
}

func (c *Client) execute(ctx context.Context, param Parameters) (bytes []byte, err error) {
	var req *http.Request
	req, err = http.NewRequestWithContext(ctx, "POST", c.opts.Gateway, strings.NewReader(param.getRequestData()))
	if err != nil {
		return
	}
	var httpResp *http.Response
	httpResp, err = c.do(req)
	if err != nil {
		return
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			log.Printf("io close error: %s\n", err.Error())
		}
	}(httpResp.Body)

	if httpResp.StatusCode != 200 {
		err = fmt.Errorf("http status code: %d", httpResp.StatusCode)
		return
	}
	bytes, err = io.ReadAll(httpResp.Body)
	return
}

func (c *Client) checkConfig() error {
	if c.opts.AppKey == "" {
		return errors.New("app key is empty")
	}
	if c.opts.AppSecret == "" {
		return errors.New("app secret is empty")
	}
	if c.opts.Gateway == "" {
		return errors.New("gateway is empty")
	}
	return nil
}

func (p Parameters) setRequestData(ctx context.Context, appKey string, appSecret string) {
	p["timestamp"] = time.Now().Format("2006-01-02 15:04:05")
	p["format"] = "json"
	p["app_key"] = appKey
	p["v"] = "2.0"
	p["sign_method"] = "md5"
	if session, ok := FromSessionContext(ctx); ok {
		p["session"] = session
	}
	p["sign"] = sign(p, appSecret)
}

func (p Parameters) getRequestData() string {
	// 公共参数
	args := url.Values{}
	// 请求参数
	for key, val := range p {
		args.Set(key, fmt.Sprint(val))
	}
	return args.Encode()
}

func sign(params Parameters, appSecret string) string {
	// 获取Key
	var keys []string
	for k := range params {
		keys = append(keys, k)
	}
	// 排序asc
	sort.Strings(keys)
	// 把所有参数名和参数值串在一起
	query := bytes.NewBufferString(appSecret)
	for _, k := range keys {
		query.WriteString(k)
		query.WriteString(interfaceToString(params[k]))
	}
	query.WriteString(appSecret)
	// 使用MD5加密
	h := md5.New()
	io.Copy(h, query)
	// 把二进制转化为大写的十六进制
	return strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
}

func interfaceToString(src interface{}) string {
	if src == nil {
		panic(errors.New("type is Nil"))
	}
	switch v := src.(type) {
	case string:
		return v
	case uint8, uint16, uint32, uint64, int, int8, int32, int64, float32, float64:
		return convert.ToString(v)
	}
	data, err := json.Marshal(src)
	if err != nil {
		panic(err)
	}
	return string(data)
}
